package com.jwater.core.metrics.system;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jwater.core.metrics.JvmMetric;
import com.jwater.core.utils.ProcessUtils;
import com.jwater.core.utils.SysType;

import jwater.org.apache.commons.io.FileUtils;
import jwater.org.apache.commons.io.IOUtils;
import jwater.org.springframework.util.StringUtils;

/**
 * @description:cpu信息获取
 * @author: jnner
 * @createDate: 2021/2/5
 * @version: 1.0
 */
public class CpuMetric {
    protected static Logger logger = LoggerFactory.getLogger(CpuMetric.class);

    private static final Pattern CPU_JIFFIES_PATTERN = Pattern.compile("cpu\\s+(.*)", Pattern.MULTILINE);
    private static Map<String, CpuTime> lastCpuTimes = new ConcurrentHashMap<>();
    private static CpuTime lastCpuTime;

    public static int cpuCount() {
        return JvmMetric.cpuCount();
    }

    /**
     * 获取所有的cpu使用率
     *
     * @return
     */
    public static List<Float> getAllCpuUsage() {
        String sysType = SysType.getSysType();
        if (SysType.WINDOWS.equals(sysType)) {
            return allCpuUsageForWin();
        } else if (SysType.LINUX.equals(sysType)) {
            return allCpuUsageForLinux();
        }
        return new ArrayList<Float>();
    }

    /**
     * 获取所有windows平台使用率
     *
     * @return
     */
    private static List<Float> allCpuUsageForWin() {
        List<Float> list = new ArrayList<Float>();
        try {
            String procCmd = System.getenv("windir") + "//system32//wbem//wmic.exe "
                    + "path Win32_PerfFormattedData_PerfOS_Processor get PercentProcessorTime /value";
            List<String> result = ProcessUtils.cmd(null, procCmd);
            for (String msg : result) {
                msg = msg.trim();
                if (!StringUtils.isEmpty(msg)) {
                    String[] items = msg.split("=");
                    if (items.length >= 2) {
                        if ("PercentProcessorTime".equals(items[0])) {
                            Integer loadPercentage = Integer.parseInt(items[1]);
                            list.add(loadPercentage * 1.0f / 100);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.toString(), e);
        }
        return list;
    }

    /**
     * 所有linux平台使用率
     *
     * @return
     */
    private static List<Float> allCpuUsageForLinux() {
        List<Float> list = new ArrayList<Float>();
        try {
            FileInputStream fis = new FileInputStream("/proc/stat");
            List<String> lines = IOUtils.readLines(fis, "UTF-8");
            fis.close();
            for (String line : lines) {
                line = line.trim();
                if (line.startsWith("cpu")) {
                    String[] items = line.split("\\s+");
                    if (!"cpu".equals(items[0])) {
                        long user = Long.parseLong(items[1]) + Long.parseLong(items[2]);
                        long system = Long.parseLong(items[3]);
                        long idle = Long.parseLong(items[4]);
                        for (int i = 5; i < items.length; i++) {
                            system += Long.parseLong(items[i]);
                        }
                        CpuTime cpuTime = new CpuTime(user, system, idle);
                        CpuTime last = lastCpuTimes.get(items[0]);
                        if (last != null) {
                            list.add(cpuTime.getCpuUsage(last));
                        }
                        lastCpuTimes.put(items[0], cpuTime);
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.toString(), e);
        }
        if (list.isEmpty()) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                logger.error(e.toString(), e);
            }
            return allCpuUsageForLinux();
        }
        return list;
    }

    /**
     * CPU总使用率
     *
     * @return
     */
    public static float getCpuUsage() {
        String sysType = SysType.getSysType();
        if (SysType.WINDOWS.equals(sysType)) {
            return cpuUsageForWin();
        } else if (SysType.LINUX.equals(sysType)) {
            return cpuUsageForLinux();
        }
        return 0f;
    }

    /**
     * windows平台cpu总使用率
     *
     * @return
     */
    private static Float cpuUsageForWin() {
        try {
            Integer loadPercentage = 0;
            String procCmd = System.getenv("windir") + "//system32//wbem//wmic.exe "
                    + "path win32_processor get LoadPercentage /value";
            List<String> result = ProcessUtils.cmd(null, procCmd);
            for (String msg : result) {
                msg = msg.trim();
                if (!msg.equals("")) {
                    String[] items = msg.split("=");
                    if (items.length >= 2) {
                        if ("LoadPercentage".equals(items[0])) {
                            loadPercentage = Integer.parseInt(items[1]);
                        }
                    }
                }
            }
            return loadPercentage * 1.0f / 100;
        } catch (Exception e) {
            logger.error(e.toString(), e);
        }
        return 0f;
    }

    /**
     * linux平台cpu总使用率
     *
     * @return
     */
    private static Float cpuUsageForLinux() {
        String[] parsedJiffies = runRegexOnFile(CPU_JIFFIES_PATTERN, "/proc/stat").split("\\s+");
        long userJiffies = Long.parseLong(parsedJiffies[0]) + Long.parseLong(parsedJiffies[1]);
        long idleJiffies = Long.parseLong(parsedJiffies[3]);
        long systemJiffies = Long.parseLong(parsedJiffies[2]);
        // this is for Linux >= 2.6
        if (parsedJiffies.length > 4) {
            for (int i = 4; i < parsedJiffies.length; i++) {
                systemJiffies += Long.parseLong(parsedJiffies[i]);
            }
        }
        CpuTime cpuTime = new CpuTime(toMillis(userJiffies), toMillis(systemJiffies), toMillis(idleJiffies));
        if (lastCpuTime == null) {
            lastCpuTime = cpuTime;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                logger.error(e.toString(), e);
            }
            return cpuUsageForLinux();
        }
        Float cpuUsage = cpuTime.getCpuUsage(lastCpuTime);
        lastCpuTime = cpuTime;
        return cpuUsage;
    }

    private static String runRegexOnFile(Pattern pattern, String filename) {
        try {
            final String file = FileUtils.readFileToString(new File(filename));
            Matcher matcher = pattern.matcher(file);
            matcher.find();
            final String firstMatch = matcher.group(1);
            if (firstMatch != null && firstMatch.length() > 0) {
                return firstMatch;
            }
        } catch (IOException e) {
            logger.error(e.toString(), e);
        }
        return null;
    }

    private static long toMillis(long jiffies) {
        int userHz = 100;
        int multiplier = 1000 / userHz;
        return jiffies * multiplier;
    }

    private static class CpuTime {
        private final long userMillis;
        private final long systemMillis;
        private final long idleMillis;

        public CpuTime(long userMillis, long systemMillis, long idleMillis) {
            this.userMillis = userMillis;
            this.systemMillis = systemMillis;
            this.idleMillis = idleMillis;
        }

        public long getTotalMillis() {
            return userMillis + systemMillis + idleMillis;
        }

        public float getCpuUsage(CpuTime previous) {
            if (previous == null) {
                return -1;
            }
            if (idleMillis == previous.idleMillis) {
                return 0f;
            }
            float usage = 1 - ((float) (idleMillis - previous.idleMillis))
                    / (float) (getTotalMillis() - previous.getTotalMillis());
            previous = this;
            return usage;
        }
    }
}
